import { camelize } from '../utils/index'

export function dispatch(vnode, oldNode) {
    let el
    // 如果 旧结点是 dom 元素就直接替换
    if (oldNode.nodeType == 1) {
        // 获取新结点
        el = parseDom(vnode)
        // 获取旧结点的父元素
        const parent = oldNode.parentNode;
        // 替换
        parent.replaceChild(el, oldNode);


        // 如果 旧结点是 虚拟 dom 的情况
    } else {
        // 如果 标签不一样 就直接替换
        if (vnode.tag !== oldNode.tag) {
            oldNode.el.parentNode.replaceChild(parseDom(vnode), oldNode.el)
        }

        // 文本 结点的判断，不相等的情况在上面判断过了
        if (!oldNode.tag) {
            if (vnode.text !== oldNode.el.text) {
                oldNode.el.textContent = vnode.text
            }
        }

        // 复用 旧结点
        el = vnode.el = oldNode.el
        // 更新属性
        updateAttr(vnode, oldNode.data)

        let oldChildren = oldNode.children || []
        let newChildren = vnode.children || []

        if (oldChildren.length > 0 && newChildren.length > 0) {
            // 新结点 和 旧结点 都有儿子， 真正的diff算法
            updateChild(oldChildren, newChildren, el)

        } else if (newChildren.length > 0) {
            // 如果 有新儿子，就添加
            for (let i = 0; i < newChildren.length; i++) {
                const child = newChildren[i]
                const realDom = parseDom(child)
                el.appendChild(realDom)
            }
        } else if (oldChildren.length > 0) {
            // 如果没有新儿子就清空
            el.innerHTML = ''
        }
    }


    return el
}

// 判断是否是同一个结点
function isSameVnode(oldNode, newNode, parent) {
    return (oldNode.tag == newNode.tag) && (oldNode.key == newNode.key)
}



/*  最复杂的 diff 算法，新元素和旧元素的头尾都有指针，
    当新旧元素一样的时候，指针就后移一位 
 
 */
function updateChild(oldChildren, newChildren) {
    let oldStartIndex = 0
    let oldStartVnode = oldChildren[oldStartIndex]
    let oldEndIndex = oldChildren.length - 1
    let oldEndVnode = oldChildren[oldEndIndex]

    let newStartIndex = 0
    let newStartVnode = newChildren[newStartIndex]
    let newEndIndex = newChildren.length - 1
    let newEndVnode = newChildren[newEndIndex]

    // 映射表
    function makeIndexByKey(children) {
        let map = {}
        children.forEach((item, index) => {
            map[item.key] = index  //{A:0, B:1, C:2}
        })
        return map
    }
    const map = makeIndexByKey(oldChildren)



    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        // 如果是 null 就应该寻找下一个
        if (!oldStartVnode) {
            oldStartVnode = oldChildren[++oldStartIndex]
        }

        else if (!oldEndIndex) {
            oldEndVnode = oldChildren[--oldEndIndex]
        }

        // 开始的是同一个元素
        if (isSameVnode(oldStartVnode, newStartVnode)) {
            // 递归 更新元素 和 属性
            dispatch(newStartVnode, oldStartVnode)
            // 指针后移
            oldStartVnode = oldChildren[++oldStartIndex]
            newStartVnode = newChildren[++newStartIndex]
        }

        // 结束的是同一个元素
        else if (isSameVnode(oldEndVnode, newEndVnode)) {
            dispatch(newEndVnode, oldEndVnode)
            // 指针后移
            oldEndVnode = oldChildren[--oldEndIndex]
            newEndVnode = newChildren[--newEndIndex]
        }

        // 老的头部 和 新的尾部比较
        else if (isSameVnode(oldStartVnode, newEndVnode)) {
            dispatch(newEndVnode, oldStartVnode)
            parent.insertBefore(oldStartVnode.el, oldStartVnode.el.nextSibling)
            oldStartVnode = oldChildren[++oldStartIndex]
            newEndVnode = newChildren[--newEndIndex]
        }

        else if (isSameVnode(oldEndVnode, newStartVnode)) {
            dispatch(newStartVnode, oldEndVnode)
            parent.insertBefore(oldEndVnode.el, oldStartVnode.el)
            oldEndVnode = oldChildren[--oldEndIndex]
            newStartVnode = newChildren[++newStartIndex]
        }

        // 两个 儿子 毫无关系
        else {
            let moveIndex = map[newStartVnode.key]

            if (moveIndex == undefined) {
                parent.insertBefore(parseDom(newStartVnode), oldStartVnode.el)
            } else {
                let moveNnode = oldChildren[moveIndex]
                oldChildren[moveIndex] = null
                parent.insertBefore(moveNnode.el, oldStartVnode.el)
                dispatch(newStartVnode, moveNnode)
            }

        }
    }


    // 如果 指针移动结束，新的开始指针 小于 新的结束指针，说明是添加新元素，需要再次依次加入
    if (newStartIndex <= newEndIndex) {
        for (let i = newStartIndex; i < newEndIndex; i++) {
            const realDom = parseDom(newChildren[i])
            parent.appendChild(realDom)
        }
    }

    if (oldStartIndex <= oldEndIndex) {
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
            const child = oldChildren[i]
            if (child != undefined) {
                parent.removeChild(child.el)
            }
        }
    }

}

export function parseDom(vnode) {
    const { text = "", tag, children = [] } = vnode
    // 元素结点
    if (typeof tag == 'string') {
        // 创建 容器
        vnode.el = document.createElement(tag);
        // 添加属性
        updateAttr(vnode)
        if (children.length) {
            children.forEach(child => {
                vnode.el.appendChild(parseDom(child))
            })

        }
    } else {
        // 文本结点
        vnode.el = document.createTextNode(text)
    }

    return vnode.el
}

// 添加属性
function updateAttr(vnode, oldProps = {}) {
    const newProps = vnode.data || {}
    let el = vnode.el;

    // 先判断  旧结点的属性  新结点有没有
    for (let key in oldProps) {
        if (!newProps[key]) {
            // 移除属性
            el.removeAttribute(key)
        }
    }

    // 处理样式
    const oldStyles = oldProps.style || {}
    const newStyles = newProps.style || {}
    for (let key in oldStyles) {
        if (!newStyles[key]) {
            el.style[key] = ''
        }
    }

    // 最后再 添加新结点的属性
    for (let key in newProps) {
        if (key == 'style') {
            const style = newProps[key]
            style.split(';').forEach(keyValue => {
                const [key, value] = keyValue.split(':');
                // 将style部分转为驼峰，并去除空格
                const newKey = camelize(key).trim()
                el.style[newKey] = value
            })
        } else if (key == 'class') {
            el.className = newProps[key]
        } else {
            el.setAttribute(key, newProps[key])
        }
    }

}
